/* Copyright 2010 Michael Wenders, michaelwenders <at> gmx <dot> de

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "lcmd_includes.h"

#include "lcmd_network.h"

LcmdNetwork::LcmdNetwork(string interface, int port)
{
	this->interface = interface;
	this->port = port;
}

LcmdNetwork::~LcmdNetwork()
{
	close(serverSocket);
}

int LcmdNetwork::init()
{
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(this->port);
	if(this->interface.length() == 0)
	{
		server.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	else
	{
		int ret = inet_aton(this->interface.c_str(), &server.sin_addr);
		if( ret == 0) // 0 = not successful, 1 = successful
		{
			return lcm::error::NET_ADDR_WRONG;
		}
	}

	serverSocket = socket(AF_INET, SOCK_STREAM, 0);

	if(serverSocket == -1)
	{
		close(serverSocket);
		return lcm::error::NET_NO_SOCKET_CREATED;
	}

	if(-1 == bind(serverSocket, (struct sockaddr*)&server, sizeof(server)))
	{
		close(serverSocket);
		return lcm::error::NET_COULD_NOT_BIND;
	}

	if( listen(serverSocket,20) == -1 )
	{
		close(serverSocket);
		return lcm::error::NET_COULD_NOT_LISTEN;
	}

	fcntl(this->serverSocket,F_SETFL, O_NONBLOCK);

	return 0;
}

int LcmdNetwork::getClientConnection(struct sockaddr_in &clientData)
{
	socklen_t len = sizeof( clientData );

	return accept(serverSocket, (struct sockaddr*)&clientData, &len);
}


int LcmdNetwork::getServerSocket()
{
	return this->serverSocket;
}

bool LcmdNetwork::checkClientsVersion(string version)
{
	stringstream ss;
	string major = ""; int imajor = 0;
	string minor = ""; int iminor = 0;
	string patch = ""; int ipatch = 0;
	string status = "";

	major = lcm::copyUntil(version,'.');
	ss << major;
	ss >> imajor;
	ss.clear();
	version = lcm::removeUntil(version,'.');

	minor = lcm::copyUntil(version,'.');
	ss << minor;
	ss >> iminor;
	ss.clear();
	version = lcm::removeUntil(version,'.');

	if(version.find_first_of('a') == string::npos)
	{ // status is beta
		patch = lcm::copyUntil(version,'b');
	}
	else
	{ // status is alpha
		patch = lcm::copyUntil(version,'a');
	}
	status = version[version.length()-1];

	ss << patch;
	ss >> ipatch;
	ss.clear();

	if(imajor != LCMD_MIN_MAJOR_VERSION ||
			iminor != LCMD_MIN_MINOR_VERSION ||
			ipatch != LCMD_MIN_PATCH_VERSION)
		return false;
	else
		return true;
}

int LcmdNetwork::initClient(int socket, string &clientname)
{
	lcm::log::Logger *logger = new lcm::log::Logger("/var/log/lcm/lcmd.log",lcm::log::LOGGER_DEBUG,lcm::log::LOGGER_INFO);
	string answer = "";
	if(lcm::readSocket(socket,answer) == 0)
	{ // got a greeting
		if(lcm::stringToInt(answer) == lcm::lcmp::LCMP_GREETING)
		{ // greeting is ok
			string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
			text += ": Client identified as LCM Client";
			logger->write(text,lcm::log::LOGGER_INFO);

			lcm::writeSocket(socket,lcm::intToString(lcm::lcmp::LCMP_VERSION));
			if(lcm::readSocket(socket,answer) == 0)
			{ // got a version
				if(LcmdNetwork::checkClientsVersion(answer))
				{ // version is ok
					string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
					text += ": Version compatible (";
					text += answer; text += ")";
					logger->write(text,lcm::log::LOGGER_INFO);

					lcm::writeSocket(socket,lcm::intToString(lcm::lcmp::LCMP_CLIENTNAME));
					if(lcm::readSocket(socket,answer) == 0)
					{ // got a hostname
						clientname = answer;
						return 0;
					}
					else
					{
						string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
						text += ": Could not read from socket";
						logger->write(text,lcm::log::LOGGER_ERROR);
					}
				} // </version is ok>
				else // version is not ok
				{
					string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
					text += ": Version incompatible (";text += answer; text += ")";
					logger->write(text,lcm::log::LOGGER_INFO);

					lcm::writeSocket(socket,lcm::intToString(lcm::lcmp::LCMP_INCOMPATIBLE));

					return lcm::NOT_OK;
				}
			} // </got a version>
			else
			{
				string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
				text += ": Could not read from socket";
				logger->write(text,lcm::log::LOGGER_ERROR);

				return lcm::NOT_OK;
			}
		} // </greeting is ok>
		else // greeting not ok
		{
			string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
			text += ": Client not identified as LCM Client; MSG was: "; text += answer;
			text += "MSG should be: "; text += lcm::lcmp::LCMP_GREETING;
			logger->write(text,lcm::log::LOGGER_ERROR);

			lcm::writeSocket(socket,lcm::intToString(lcm::lcmp::LCMP_DENY));

			return lcm::NOT_OK;
		}
	} // </got a greeting>
	else
	{
		string text = "Thread "; text += lcm::intToString((pid_t) syscall (SYS_gettid));
		text += ": Could not read from socket";
		logger->write(text,lcm::log::LOGGER_ERROR);

		return lcm::NOT_OK;
	}

	return lcm::OK;
}

int LcmdNetwork::sendTextFile(int socket, string filename)
{
	ifstream file;
	file.open(filename.c_str(),ios::in);
	if(!file)
		return lcm::NOT_OK;

	string buffer = "";
	while(!file.eof())
	{
		char c = 0;
		c = file.get();
		if(c >= 0)
		{
			buffer += c;
		}
	}
	buffer += '\0';
	file.close();

	switch(write(socket,buffer.c_str(),buffer.length()))
	{
	case -1:
		return lcm::NOT_OK;
	case 0:
		return lcm::NOT_OK;
	default:
		return lcm::OK;
	}

}
